import sys
from pathlib import Path
from datetime import datetime

import pygame

from . import component as com
from . import util

"""
类：棋盘、棋子
逻辑：判断是否可下、判断是否胜利、判断是否禁手
"""
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
CELL_WIDTH = 36  # 建议值：36
NO_WIN = [-1, []]
PATH = str(Path(__file__).resolve().parent)


class Piece:
    """棋子"""
    radius = CELL_WIDTH * 0.42

    def __init__(self, player: int, x: int, y: int):
        self.player = player
        self.x = x
        self.y = y
        if player == 0:
            self.color = BLACK
        else:
            self.color = WHITE


class Board:
    """棋盘"""
    board_color = (216, 169, 101)
    line_color = (0, 0, 0)
    cell_width = CELL_WIDTH  # 棋格宽度
    line_width = 2  # 线条宽度
    line_len = CELL_WIDTH * 14  # 线条的长度
    board_width = CELL_WIDTH * 14 + CELL_WIDTH  # 多出的 CELL_WIDTH 是边框


class Game:
    """游戏数据、游戏逻辑"""
    board = Board()
    screen_size = (board.board_width * 2, board.board_width * 1.2)
    piece_on_board: list[Piece] = []  # 在棋盘上的棋子
    piece_undo_stack: list[Piece] = []  # 放悔棋的栈
    piece_matrix = [[-1] * 15 for _ in range(15)]  # 棋子矩阵
    player = 0  # 下一次落子，轮到谁

    # 赢家和他的数据
    win_player_and_data = NO_WIN
    win_player0_count = 0  # 赢的次数
    win_player1_count = 0

    # 放置按钮主键，共享，以便于在事件循环中使用
    btn_undo: com.Button = None
    btn_redo: com.Button = None
    btn_start: com.Button = None
    btn_next: com.Button = None

    # 记录时间
    time_start_all: datetime = None  # 进入游戏时间
    time_start_one: datetime = None  # 进入一局开始时间
    time_start_one_stop: datetime = None  # 每一局停止的时间

    @staticmethod
    def start():
        Game.piece_on_board = []
        Game.piece_undo_stack = []
        Game.piece_matrix = [[-1] * 15 for _ in range(15)]
        Game.player = 0  # 0黑1白
        Game.win_player_and_data = NO_WIN
        Game.time_start_one = datetime.now()
        Game.time_start_one_stop = None

    @staticmethod
    def next_game():
        # 增加黑白各自赢的次数
        if Game.win_player_and_data[0] == 0:
            Game.win_player0_count += 1
        elif Game.win_player_and_data[0] == 1:
            Game.win_player1_count += 1
        Game.start()

    @staticmethod
    def find_x_y(pos_x: int, pos_y: int) -> tuple[int, int]:
        """找到棋子 x y"""
        board = Game.board
        # 偏移量
        dx = board.board_width * 0.2 + board.cell_width / 2
        dy = board.board_width * 0.1 + board.cell_width / 2
        cell_w = board.cell_width
        r = cell_w / 2  # 半径
        x = int((pos_x - dx + r) / cell_w)
        y = int((pos_y - dy + r) / cell_w)

        if x < 0 or y < 0 or x > 14 or y > 14:
            return -1, -1
        else:
            return x, y

    @staticmethod
    def play_a_move(piece: Piece):
        """下棋"""
        x = piece.x
        y = piece.y
        if Game.piece_matrix[x][y] == -1:
            Game.piece_matrix[x][y] = piece.player
            Game.piece_on_board.append(piece)
            Game.piece_undo_stack.clear()  # 必须清空悔棋的栈
            # 切换下一次下棋的角色
            Game.player = 1 if Game.player == 0 else 0

    @staticmethod
    def undo_a_move():
        """悔棋"""
        if len(Game.piece_on_board):
            piece = Game.piece_on_board.pop()
            x = piece.x
            y = piece.y
            Game.piece_matrix[x][y] = -1
            Game.piece_undo_stack.append(piece)  # 放在悔棋的栈
            Game.player = 1 if Game.player == 0 else 0

    @staticmethod
    def redo_a_move():
        """撤销悔棋"""
        if len(Game.piece_undo_stack):
            piece = Game.piece_undo_stack.pop()
            x = piece.x
            y = piece.y
            Game.piece_matrix[x][y] = piece.player
            Game.piece_on_board.append(piece)  # 放在棋盘上
            Game.player = 1 if Game.player == 0 else 0

    @staticmethod
    def check_win(piece: Piece):
        m = Game.piece_matrix
        arr = []
        player, x, y = piece.player, piece.x, piece.y
        arr.append((x, y))

        directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
        for dr, dc in directions:
            count = 1
            r, c = x + dr, y + dc
            while 0 <= r < 15 and 0 <= c < 15 and m[r][c] == player:
                count += 1
                arr.append((r, c))
                r += dr
                c += dc
            r, c = x - dr, y - dc
            while 0 <= r < 15 and 0 <= c < 15 and m[r][c] == player:
                count += 1
                arr.append((r, c))
                r -= dr
                c -= dc
            if count == 5:
                arr.sort(key=lambda item: item[0])
                return [player, arr]
        return NO_WIN

    @staticmethod
    def check_forbidden(piece: Piece):
        """禁手检测优化版（数值映射：-1=空，0=黑棋，1=白棋）"""
        player, x, y = piece.player, piece.x, piece.y
        if player != 0:  # 仅黑棋需要检查禁手
            return 0

        m = Game.piece_matrix
        live_three_count = 0
        live_four_count = 0
        directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
        long_chain = False

        # 长连禁手检测
        for dr, dc in directions:
            stones = 1
            for delta in [1, -1]:  # 双向检测
                r, c = x + dr * delta, y + dc * delta
                while 0 <= r < 15 and 0 <= c < 15:
                    if m[r][c] == 0:  # 黑棋
                        stones += 1
                        r += dr * delta
                        c += dc * delta
                    else:
                        break
            if stones > 5:
                long_chain = True

        if long_chain:
            return 3

        # 三三/四四禁手检测
        for dr, dc in directions:
            # 正方向检测
            stones, space = 1, 0
            r, c = x + dr, y + dc
            while 0 <= r < 15 and 0 <= c < 15 and space < 2:
                if m[r][c] == 0:
                    stones += 1
                elif m[r][c] == -1 and m[r - dr][c - dc] == 0:
                    if 1 <= r < 14 and 1 <= c < 14 and m[r + dr][c + dc] == 0:
                        space += 1
                    else:
                        break
                else:  # 遇到白棋
                    break
                r += dr
                c += dc

            # 活性判断
            ends_free1 = 0 <= r < 15 and 0 <= c < 15 and m[r][c] == -1

            # 反方向检测
            if space == 2:
                space = 0
            r, c = x - dr, y - dc
            while 0 <= r < 15 and 0 <= c < 15 and space < 2:
                if m[r][c] == 0:
                    stones += 1
                elif m[r][c] == -1 and m[r + dr][c + dc] == 0:
                    if 1 <= r < 14 and 1 <= c < 14 and m[r - dr][c - dc] == 0:
                        space += 1
                    else:
                        break
                else:
                    break
                r -= dr
                c -= dc

            # 活性判断
            ends_free2 = 0 <= r < 15 and 0 <= c < 15 and m[r][c] == -1
            if stones == 3 and space <= 1 and ends_free1 and ends_free2:
                live_three_count += 1
            elif stones == 4 and space == 0 and (ends_free1 or ends_free2):
                live_four_count += 1

        if live_three_count >= 2:
            return 1
        if live_four_count >= 2:
            return 2
        return 0

    @staticmethod
    def draw_board(screen):
        board = Game.board
        # 棋盘背景
        pygame.draw.rect(screen, board.board_color,
                         (board.board_width * 0.2, board.board_width * 0.1, board.board_width, board.board_width))

        # 偏移量
        dx = board.board_width * 0.2 + board.cell_width / 2
        dy = board.board_width * 0.1 + board.cell_width / 2
        # 绘制棋盘线条
        for i in range(15):
            pygame.draw.line(screen, board.line_color,
                             (dx, dy + board.cell_width * i),
                             (dx + board.line_len, dy + board.cell_width * i),
                             board.line_width)
            pygame.draw.line(screen, board.line_color,
                             (dx + board.cell_width * i, dy),
                             (dx + board.cell_width * i, dy + board.line_len),
                             board.line_width)

        # 绘制棋盘上的星点
        cell_width = board.cell_width
        star_list = [(dx + x * cell_width, dy + y * cell_width)
                     for x in [3, 11] for y in [3, 11]]
        star_list.append((dx + 7 * cell_width, dy + 7 * cell_width))
        for star in star_list:
            pygame.draw.circle(screen, board.line_color,
                               star, board.line_width * 3)

    @staticmethod
    def draw_piece(screen):
        board = Game.board
        # 偏移量
        dx = board.board_width * 0.2 + board.cell_width / 2
        dy = board.board_width * 0.1 + board.cell_width / 2
        # 绘制已经下的棋子
        for piece in Game.piece_on_board:
            x = dx + piece.x * board.cell_width
            y = dy + piece.y * board.cell_width
            pygame.draw.circle(screen, piece.color, (x, y), piece.radius)

    @staticmethod
    def draw_side(screen):
        board = Game.board
        dx = board.board_width * 1.2 + board.cell_width
        dy = board.board_width * 0.1
        # 绘制白色半透明矩形
        rect_surface = pygame.Surface((board.board_width * 0.6, board.board_width),
                                      pygame.SRCALPHA)  # 用于支持透明度
        rect_surface.set_alpha(180)
        pygame.draw.rect(rect_surface, (255, 255, 255),
                         rect_surface.get_rect())
        screen.blit(rect_surface, (dx, dy))

    @staticmethod
    def draw_text(screen: pygame.Surface):
        board = Game.board
        dx = board.board_width * 1.2 + board.cell_width * 1.5
        dy = board.board_width * 0.1 + board.cell_width / 2
        font_size = int(board.cell_width * 0.6)
        line_height = font_size * 1.5
        """绘制文本"""
        now = datetime.now()
        if Game.win_player_and_data[0] != -1:
            dt2 = Game.time_start_one_stop - Game.time_start_one
        else:
            dt2 = now - Game.time_start_one
            Game.time_start_one_stop = now
        dt3 = now - Game.time_start_all
        text2 = f'本局时长：{util.seconds_to_hms(dt2.seconds)}'
        text3 = f'总计时长：{util.seconds_to_hms(dt3.seconds)}'
        text4 = f'黑 : 白 = {Game.win_player0_count} : {Game.win_player1_count}'

        if Game.player == 0:
            text1 = '轮到黑子'
        else:
            text1 = '轮到白子'

        text_color = (0, 0, 0)
        font = pygame.font.Font(util.get_resource_path(
            'gobang/font/STLITI.TTF'), font_size)

        text_surface1 = font.render(text1, True, text_color)
        text_surface2 = font.render(text2, True, text_color)
        text_surface3 = font.render(text3, True, text_color)
        text_surface4 = font.render(text4, True, text_color)

        screen.blit(text_surface1, (dx, dy))
        screen.blit(text_surface2, (dx, dy + line_height))
        screen.blit(text_surface3, (dx, dy + line_height * 2))
        screen.blit(text_surface4, (dx, dy + line_height * 3))

    @staticmethod
    def draw_button():
        board = Game.board
        dx = board.board_width * 1.2 + board.cell_width * 2.1
        dy = board.board_width * 0.1
        width = board.cell_width * 2
        height = board.cell_width * 1
        t_size = int(board.cell_width / 2)
        btn_y = dy + board.board_width * 0.7
        btn_undo = com.Button(dx, btn_y, width, height, '悔棋', t_size)
        btn_redo = com.Button(dx + width * 1.2, btn_y,
                              width, height, '重做', t_size)
        btn_start = com.Button(dx + width * 2.4, btn_y,
                               width, height, '重玩', t_size)
        # 这个btn_next按钮在赢的时候才会显示
        btn_next = com.Button(dx, btn_y, width * 3.4, height, '下一局', t_size)

        # 将对象地址放在 Game 上，便于后面添加点击事件，在 main 函数中的事件循环上绑定事件
        Game.btn_undo = btn_undo
        Game.btn_redo = btn_redo
        Game.btn_start = btn_start
        Game.btn_next = btn_next

    @staticmethod
    def draw_logo(screen: pygame.Surface):
        board = Game.board
        dx = board.board_width * 1.2 + board.cell_width
        dy = board.board_width * 0.1
        logo_img = pygame.image.load(
            util.get_resource_path('gobang/image/logo.png'))
        # 指定新的宽度
        new_width = board.board_width * 0.6
        scaled_win_img = util.scaled_img_by_width(logo_img, new_width)
        screen.blit(scaled_win_img, (dx, dy + board.board_width * 0.35))

    @staticmethod
    def show_win_player(screen: pygame.Surface, win_player: int, win_x_y: list[list[int]]):
        board = Game.board

        # 2.1. 显示胜利的棋子颜色
        dx = board.board_width * 1.2 + board.cell_width
        dy = board.board_width * 0.1
        win_text = '黑子' if win_player == 0 else '白子'
        font_size = int(board.cell_width)
        font = pygame.font.Font(util.get_resource_path(
            'gobang/font/STLITI.TTF'), font_size)
        text_surface = font.render(win_text, True, (0, 0, 0))
        screen.blit(text_surface, (dx + board.cell_width /
                    2, dy + board.board_width * 0.35))

        # 2.2. 显示禁手信息
        forbidden_text = ''
        if len(win_x_y) <= 5:
            if win_x_y[0] == 1:
                forbidden_text = '黑子违反三三禁手'
            elif win_x_y[0] == 2:
                forbidden_text = '黑子违反四四禁手'
            elif win_x_y[0] == 3:
                forbidden_text = '黑子违反长连禁手'

            forbidden_surface = font.render(forbidden_text, True, (0, 0, 0))
            screen.blit(forbidden_surface, (dx + board.cell_width /
                        2, dy + board.board_width * 0.28))

        # 3. 显示胜利的图片
        win_img = pygame.image.load(
            util.get_resource_path('gobang/image/win.png'))
        # 指定新的宽度
        new_width = board.board_width * 0.6
        scaled_win_img = util.scaled_img_by_width(win_img, new_width)
        screen.blit(scaled_win_img, (dx, dy + board.board_width * 0.4))

        if len(win_x_y) >= 5:
            # 黑子下到禁手的情况，这时候不要绘制胜利线条
            # 1. 绘制胜利的线条
            dx = board.board_width * 0.2 + board.cell_width / 2
            dy = board.board_width * 0.1 + board.cell_width / 2
            piece1 = win_x_y[0]
            piece2 = win_x_y[4]
            x1 = dx + piece1[0] * board.cell_width
            y1 = dy + piece1[1] * board.cell_width
            x2 = dx + piece2[0] * board.cell_width
            y2 = dy + piece2[1] * board.cell_width
            pygame.draw.line(screen, (255, 0, 0), (x1, y1),
                             (x2, y2), board.line_width * 3)


def main():
    Game.time_start_all = datetime.now()
    Game.start()

    # 游戏数据
    bg_img = pygame.image.load(util.get_resource_path('gobang/image/bg.jpeg'))
    scaled_bg_img = pygame.transform.scale(bg_img, Game.screen_size)
    scaled_bg_img.set_alpha(200)

    clock = pygame.time.Clock()
    pygame.init()
    pygame.display.set_caption('五子棋')
    screen = pygame.display.set_mode(Game.screen_size)
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONUP:
                if Game.btn_undo and Game.btn_redo and Game.btn_start and Game.btn_next:
                    if Game.win_player_and_data[0] == -1:
                        # 已经胜利，禁止悔棋、重做、重玩
                        Game.btn_undo.handle_event(event, Game.undo_a_move)
                        Game.btn_redo.handle_event(event, Game.redo_a_move)
                        Game.btn_start.handle_event(event, Game.start)
                    else:
                        Game.btn_next.handle_event(event, Game.next_game)
                    if Game.win_player_and_data[0] != -1:
                        continue  # 禁止继续下棋
                    x, y = Game.find_x_y(*pygame.mouse.get_pos())
                    if Game.piece_matrix[x][y] == -1 and (x != -1 or y != -1):
                        new_piece = Piece(Game.player, x, y)
                        Game.play_a_move(new_piece)
                        forbidden = Game.check_forbidden(new_piece)

                        if forbidden == 0:
                            Game.win_player_and_data = Game.check_win(
                                new_piece)
                        else:
                            Game.win_player_and_data = [1, [forbidden]]

        screen.fill(WHITE)
        screen.blit(scaled_bg_img, (0, 0))

        # 绘制棋盘
        Game.draw_board(screen)
        # 绘制棋盘上的棋子
        Game.draw_piece(screen)
        # 绘制侧边栏的白色半透明背景
        Game.draw_side(screen)
        # 绘制侧边栏的文本
        Game.draw_text(screen)
        # 绘制各种个按钮
        Game.draw_button()

        # 展示胜利信息
        if Game.win_player_and_data[0] != -1:
            Game.btn_next.draw(screen)  # 显示下一局的按钮
            Game.show_win_player(screen, *Game.win_player_and_data)
        else:
            Game.btn_undo.draw(screen)
            Game.btn_redo.draw(screen)
            Game.btn_start.draw(screen)
            Game.draw_logo(screen)

        pygame.display.flip()
        clock.tick(60)
